#include "ZerglingController.h"
#include <cmath>
#include <algorithm>

using namespace std;

const float zerglingSize = 16.0f + 8.0f;
const float distanceBetweenLines = 50.0f;
ZerglingController ZerglingController::INSTANCE;

ZerglingController ZerglingController::getInstance()
{
	return INSTANCE;
}

ZerglingController::ZerglingController()
{
}

void ZerglingController::upkeep()
{
	for (std::vector<ZerglingGroupState>::iterator it = fss.begin(); it != fss.end(); it++)
	{
		(*it).preformGroupState();
	}
}

void ZerglingController::free(std::set<BWAPI::Unit*> & units)
{
	for (std::set<BWAPI::Unit*>::iterator it = units.begin(); it != units.end(); it++)
	{
		for (std::vector<ZerglingGroupState>::iterator jt = fss.begin(); jt != fss.end(); jt++)
		{
			(*jt).removeFromState((*it));
		}
	}
}

/**
 * Forms a line of zerglings. Returns false if something horrible happened, or no zerglings were sent.
 */
bool ZerglingController::formLine(vector<BWAPI::Unit*> & units,
								  int x1, int y1,
								  int x2, int y2,
								  int awayX, int awayY)
{
	const int rise = y2-y1;
	const int run = x2-x1;
	if (units.size() < 1) return false;
	SLine myLine(x1, y1, x2, y2);
	SLine awayLine(x1, y1, awayX, awayY);
	int originalX = x1;
	int originalY = y1;
	float distanceNew, distanceOld, newX, newY;
	int lineNum = 0;
	bool horzCase = (myLine.slope == 0);
	bool vertCase = (myLine.slope != myLine.slope);
	pair<float, float> delta;
	pair<float, float> deltaAway;

	if (horzCase)
	{
		formHorizLine(units, y1, x1, x2,awayX, awayY);
		return true;
	}
	if (vertCase)
	{
		formVertLine(units, x1, y1, y2, awayX, awayY);
		return true;
	}

	vector<BWAPI::Unit*>::iterator it = units.begin();

	try
	{
		delta = findDelta(myLine, zerglingSize);
		deltaAway = findDeltaAway(awayLine, distanceBetweenLines);
	} catch (exception &e)
	{
		BWAPI::Broodwar->printf(e.what());
		return false;
	}

	while (it != units.end()) {
		x1 = (int) floor(originalX + lineNum*deltaAway.first);
		y1 = (int) floor(originalY + lineNum*deltaAway.second);
		x2 = x1+run;
		y2 = y1+rise;
		newX = (float) x1;
		newY = (float) y1;
		distanceNew = (float) (x2-x1) * (float) (x2-x1) + (float) (y2-y1) * (float) (y2-y1);
		distanceOld = distanceNew+1;
		ZerglingGroupState zs(new ZerglingStateAttack(x1,y1,awayX, awayY, (int) floor(delta.first), (int) floor(delta.second)));
		zs.addToGroupState(*it);
		++it;
		
		while (it != units.end() && distanceOld > distanceNew)
		{
			zs.addToGroupState(*it);
			distanceOld = distanceNew;
			newX = newX+delta.first;
			newY = newY+delta.second;
			x1 = (int) ceil(newX);
			y1 = (int) ceil(newY);
			++it;
			distanceNew = (float) (x2-x1) * (float) (x2-x1) + (float) (y2-y1) * (float) (y2-y1);
		}
		fss.push_back(zs);
		zs.preformGroupState();
		lineNum++;
	}

	return true;
}

void ZerglingController::attack(set<BWAPI::Unit*> &units,
								int x, int y)
{
	free(units);

	if (detectFlank(units, x, y)) {
		return;
	}
	//Figure out where a line should be
	int lineStartX = 2330;
	int lineStartY = 1150;
	int lineEndX = 2230;
	int lineEndY = 1410;

	vector<BWAPI::Unit*> lineUnits;
	prepareForLine(units, lineEndX, lineEndY, lineUnits);
	formLine(lineUnits, lineStartX, lineStartY, lineEndX, lineEndY, x, y);
}

void ZerglingController::move(set<BWAPI::Unit*> &units,
								int x, int y)
{
	free(units);
	ZerglingGroupState z(new ZerglingStateRetreat(x, y));
	for (set<BWAPI::Unit*>::iterator it = units.begin(); it != units.end(); it++)
	{
		z.addToGroupState((*it));
	}
	fss.push_back(z);
	z.preformGroupState();
}

bool ZerglingController::detectFlank(set<BWAPI::Unit*> &units,
									 int x, int y)
{
	//Find the angle between (x,y) and unit position, relative to a circle of radius (distance).
	//Split units into x lines, based on angle
	//Order to flank group
	//Return true

	//else
	return false;
}

void ZerglingController::flankGroup(vector<BWAPI::Unit*> &units1,
									vector<BWAPI::Unit*> &units2,
									int x1, int y1,
									int x2, int y2)
{
	int units1_X1, units2_X1, units1_Y1, units2_Y1,
		units1_X2, units2_X2, units1_Y2, units2_Y2;
	//Figure out where line one is
	//Figure out where line two is

	formLine(units1, units1_X1, units1_Y1, units1_X2, units1_Y2, x1, y1);
	formLine(units2, units2_X1, units2_Y1, units2_X2, units2_Y2, x2, y2);
}


void ZerglingController::prepareForLine(set<BWAPI::Unit*> & units,
											int endX, int endY,
											vector<BWAPI::Unit*> & result)
{
	vector<BWAPI::Unit*> v(units.begin(), units.end());
	sort(v.begin(), v.end(), lineSortFunctor());
	result = v;
}

std::pair<float, float> ZerglingController::findDelta(SLine line, float distance)
{
	SQuadValues myValues = findQuadraticValues(line, distance);
	if ((myValues.b*myValues.b - 4*myValues.a*myValues.c) <= 0)
		throw exception("Only Complex roots found");
	pair<float, float> delta;
	float a = myValues.a, b = myValues.b, c = myValues.c;
	int x = line.x2, y = line.y2;
	float deltaXArg1 = ((-b) + sqrt(b*b-4*a*c)) / (2.0f*a);
	float deltaXArg2 = ((-b) - sqrt(b*b-4*a*c)) / (2.0f*a);
	float deltaYArg1 = deltaXArg1 * line.slope + line.intercept;
	float deltaYArg2 = deltaXArg2 * line.slope + line.intercept;
	
	float distanceXArg1 = (deltaXArg1 - x) * (deltaXArg1 - x) + (deltaYArg1 - y) * (deltaYArg1 - y);
	float distanceXArg2 = (deltaXArg2 - x) * (deltaXArg2 - x) + (deltaYArg2 - y) * (deltaYArg2 - y);
	if (distanceXArg1 <= distanceXArg2) {
		delta.first = deltaXArg1 - line.x1;
		delta.second = deltaYArg1 - line.y1;
	} else {
		delta.first = deltaXArg2 - line.x1;
		delta.second = deltaYArg2 - line.y1;
	}
	return delta;
}

std::pair<float, float> ZerglingController::findDeltaAway(SLine line, float distance)
{
	SQuadValues myValues = findQuadraticValues(line, distance);
	if ((myValues.b*myValues.b - 4*myValues.a*myValues.c) <= 0)
		throw exception("Only Complex roots found");
	pair<float, float> delta;
	float a = myValues.a, b = myValues.b, c = myValues.c;
	int x = line.x2, y = line.y2;
	float minValue = 4.0f*distance/5.0f;
	float deltaXArg1 = ((-b) + sqrt(b*b-4*a*c)) / (2.0f*a);
	float deltaXArg2 = ((-b) - sqrt(b*b-4*a*c)) / (2.0f*a);
	float deltaYArg1 = deltaXArg1 * line.slope + line.intercept;
	float deltaYArg2 = deltaXArg2 * line.slope + line.intercept;
	
	float distanceXArg1 = (deltaXArg1 - x) * (deltaXArg1 - x) + (deltaYArg1 - y) * (deltaYArg1 - y);
	float distanceXArg2 = (deltaXArg2 - x) * (deltaXArg2 - x) + (deltaYArg2 - y) * (deltaYArg2 - y);
	if (distanceXArg1 >= distanceXArg2) {
		if ((deltaXArg1-line.x1) > 0) {
			delta.first = max(deltaXArg1 - line.x1, minValue);
			delta.second = max(deltaYArg1 - line.y1, minValue);
		} else {
			delta.first = min(deltaXArg1 - line.x1, -minValue);
			delta.second = min(deltaYArg1 - line.y1, -minValue);
		}
	} else {
		if ((deltaXArg2-line.x1) > 0) {
			delta.first = max(deltaXArg2 - line.x1, minValue);
			delta.second = max(deltaYArg2 - line.y1, minValue);
		} else {
			delta.first = min(deltaXArg2 - line.x1, -minValue);
			delta.second = min(deltaYArg2 - line.y1, -minValue);
		}
	}
	return delta;
}

SQuadValues ZerglingController::findQuadraticValues(SLine line, float distance)
{
	float a = (1.0f+line.slope*line.slope);
	float b = (-2.0f * line.slope * line.slope * line.x1 - 2.0f * line.x1);
	float c = (line.x1*line.x1*(1+line.slope*line.slope)-distance*distance);
	return SQuadValues(a,b,c);
}

void ZerglingController::formHorizLine(std::vector<BWAPI::Unit*> & units, int y, int x1, int x2, int awayX, int awayY)
{
	std::vector<BWAPI::Unit*>::iterator it = units.begin();
	int run = x2-x1;
	int deltaX = (int) floor(zerglingSize);
	int changeY = (int) floor(distanceBetweenLines);
	if ((awayY-y) >= 0)
	{
		changeY = -changeY;
	}
	if (run <0 ) {
		deltaX = -deltaX;
	}
	int lineNum = 0;
	
	int originalX = x1;
	int originalY = y;
	int distanceNew, distanceOld;

	while (it != units.end()) {
		x1 = originalX;
		y = originalY + lineNum * changeY;
		x2 = x1+run;
		distanceNew = (x2-x1) * (x2-x1);
		distanceOld = distanceNew+1;

		ZerglingGroupState zs(new ZerglingStateAttack(x1,y,awayX, awayY, deltaX, 0));

		(*it)->move(BWAPI::Position(x1, y));
		++it;

		while (it != units.end() && distanceOld > distanceNew)
		{
			distanceOld = distanceNew;
			zs.addToGroupState(*it);
			x1 += deltaX;
			++it;
			distanceNew = (x2-x1) * (x2-x1);
		}
		fss.push_back(zs);
		zs.preformGroupState();
		lineNum++;
	}
}

void ZerglingController::formVertLine(std::vector<BWAPI::Unit*> & units, int x, int y1, int y2, int awayX, int awayY)
{
	std::vector<BWAPI::Unit*>::iterator it = units.begin();
	int rise = y2-y1;
	int deltaY = (int) floor(zerglingSize);
	int changeX = (int) floor(distanceBetweenLines);
	if ((awayX-x) >= 0)
	{
		changeX = -changeX;
	}
	if (rise <0 ) {
		deltaY = -deltaY;
	}
	int lineNum = 0;
	
	int originalX = x;
	int originalY = y1;
	int distanceNew, distanceOld;

	while (it != units.end()) {
		y1 = originalY;
		x = originalX + lineNum * changeX;
		y2 = y1+rise;
		distanceNew = (y2-y1) * (y2-y1);
		distanceOld = distanceNew+1;

		ZerglingGroupState zs(new ZerglingStateAttack(x,y1,awayX, awayY, 0, deltaY));

		(*it)->move(BWAPI::Position(x, y1));
		++it;

		while (it != units.end() && distanceOld > distanceNew)
		{
			distanceOld = distanceNew;
			zs.addToGroupState(*it);
			y1 += deltaY;
			++it;
			distanceNew = (y2-y1) * (y2-y1);
		}

		fss.push_back(zs);
		zs.preformGroupState();
		lineNum++;
	}
}